﻿using LightCAD.Core;
using LightCAD.Core.Elements;
using LightCAD.Runtime;
using SkiaSharp;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace LightCAD.Drawing
{
    public class CircleAction : ElementAction
    {
        public static string CommandName;
        public static LcCreateMethod[] CreateMethods;

        static CircleAction()
        {
            CommandName = "CIRCLE";
            CreateMethods = new LcCreateMethod[3];
            CreateMethods[0] = new LcCreateMethod()
            {
                Name = "CR",
                Description = "圆心半径画圆",
                Steps = new LcCreateStep[]
                {
                    new LcCreateStep{ Name="SpecifyCenter", Options="指定圆的圆心或[三点(3P) 二点(2P) 切点,切点,半径(T)]:" },
                    new LcCreateStep{ Name="SpecifyRadius", Options="指定圆的半径或[直径(D)]:" },
                    new LcCreateStep{ Name="SpecifyDiameter",Options="指定圆的直径:"},
                }
            };
            CreateMethods[1] = new LcCreateMethod()
            {
                Name = "3P",
                Description = "三点画圆",
                Steps = new LcCreateStep[]
                {
                    new LcCreateStep{ Name="SpecifyPoint1", Options="指定圆直径的第一个端点:" },
                    new LcCreateStep{ Name="SpecifyPoint2", Options="指定圆直径的第二个端点:" },
                    new LcCreateStep{ Name="SpecifyPoint3", Options="指定圆直径的第三个端点:" }
                }
            };
            CreateMethods[2] = new LcCreateMethod()
            {
                Name = "2P",
                Description = "二点画圆",
                Steps = new LcCreateStep[]
                {
                    new LcCreateStep{ Name="SpecifyPoint1", Options="指定圆直径的第一个端点:" },
                    new LcCreateStep{ Name="SpecifyPoint2", Options="指定圆直径的第二个端点:" },
                }
            };
        }

        internal static void Initilize()
        {
            ElementActions.Circle = new CircleAction();
            LcDocument.ElementActions.Add(BuiltinElementType.Circle, ElementActions.Circle);
        }

        private PointInputer inputer { get; set; }

        private Vector2d? cr_center;
        private Vector2d? cr_point;
        private Vector2d? _point1;
        private Vector2d? _point2;
        private Vector2d? _point3;
        private Vector2d _tangent1;
        private Vector2d _tangent2;
        private string _methodName;
        private string _methodArg;
        public CircleAction() { }
        public CircleAction(DocumentRuntime docRt) : base(docRt)
        {
            docRt.CommandCtrl.WriteInfo("命令：" + CommandName);
        }
        private LcCreateMethod GetMethod(string method)
        {
            if (method == null) return CreateMethods[0];
            var getted = CreateMethods.FirstOrDefault((m) => m.Name == method);
            if (getted == null)
                return CreateMethods[0];
            else
                return getted;
        }
        public async void ExecCreate(string[] args = null)
        {
            var method = "CR";
            if (args != null && args.Length > 0)
            {
                method = args[0];
            }
            var curMethod = this.GetMethod(method);
            _methodName = curMethod.Name;
            this.inputer = new PointInputer(this.docRt);
            this.StartCreating();
            if (curMethod.Name == "CR") goto Method_CR_Step0;
            if (curMethod.Name == "2P") goto Method_2P_Step0;
            if (curMethod.Name == "3P") goto Method_3P_Step0;
            return;

        Method_CR_Step0:
            var cr_step0 = curMethod.Steps[0];
            _methodName = curMethod.Name;
            var cr_result0 = await inputer.Execute(cr_step0.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (cr_result0.Value == null)
            {
                if (cr_result0.Option != null && cr_result0.Option.ToUpper() == "2P")
                {
                    goto Method_2P_Step0;
                }
                else if (cr_result0.Option != null && cr_result0.Option.ToUpper() == "3P")
                    goto Method_3P_Step0;
            }
            this.cr_center = (Vector2d)cr_result0.Value;

        Method_CR_Step1:
            var cr_step1 = curMethod.Steps[1];
            var cr_result1 = await inputer.Execute(cr_step1.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (cr_result1.Value == null)
            {
                if (cr_result1.Option == "D")
                {
                    this._methodArg = "D";
                    goto Method_CR_Step1;
                }
            }
            this.cr_point = (Vector2d)cr_result1.Value;
            goto End;


        Method_2P_Step0:
            curMethod = this.GetMethod("2P");
            _methodName = curMethod.Name;
            var _2p_step0 = curMethod.Steps[0];
            var _2p_result0 = await inputer.Execute(_2p_step0.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (_2p_result0.Value == null)
            {
                goto Method_2P_Step0;
            }
            this._point1 = (Vector2d)_2p_result0.Value;

        Method_2P_Step1:
            var _2p_step1 = curMethod.Steps[1];
            var _2p_result1 = await inputer.Execute(_2p_step1.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (_2p_result1.Value == null)
            {
                goto Method_2P_Step1;
            }
            this._point2 = (Vector2d)_2p_result1.Value;
            goto End;


        Method_3P_Step0:
            curMethod = this.GetMethod("3P");
            _methodName = curMethod.Name;
            var _3p_step0 = curMethod.Steps[0];
            var _3p_result0 = await inputer.Execute(_3p_step0.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (_3p_result0.Value == null)
            {
                goto Method_3P_Step0;
            }
            this._point1 = (Vector2d)_3p_result0.Value;
        Method_3P_Step1:
            var _3p_step1 = curMethod.Steps[1];
            var _3p_result1 = await inputer.Execute(_3p_step1.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (_3p_result1.Value == null)
            {
                goto Method_3P_Step1;
            }
            this._point2 = (Vector2d)_3p_result1.Value;
        Method_3P_Step2:
            var _3p_step2 = curMethod.Steps[2];
            var _3p_result2 = await inputer.Execute(_3p_step2.Options);
            if (inputer.isCancelled) { this.Cancel(); return; }
            if (_3p_result2.Value == null)
            {
                goto Method_3P_Step2;
            }
            this._point3 = (Vector2d)_3p_result2.Value;
            goto End;
        End:
            CreateCircleElement();
            this.inputer = null;
            this.EndCreating();
        }
        public override void Cancel()
        {
            base.Cancel();
            this.vportRt.SetCreateDrawer(null);
        }
        private void CreateCircleElement()
        {
            if (_methodName == "CR")
            {
                if (_methodArg == "D")
                    this.vportRt.ActiveElementSet.AddCircle(cr_center.Value, Vector2d.Distance(cr_point.Value, cr_center.Value) / 2);
                else
                    this.vportRt.ActiveElementSet.AddCircle(cr_center.Value, Vector2d.Distance(cr_point.Value, cr_center.Value));
            }
            else if (_methodName == "2P")
            {
                var c = (_point1 + _point2) / 2;
                var r = Vector2d.Distance(_point1.Value, _point2.Value) / 2;
                this.vportRt.ActiveElementSet.AddCircle(c.Value, r);
            }
            else if (_methodName == "3P")
            {
                var valid = getCircleBy3P(_point1.Value, _point2.Value, _point3.Value, out var c, out var r);
                if (valid)
                    this.vportRt.ActiveElementSet.AddCircle(c.Value, r);
                else
                    Debug.Assert(valid, "三点共线无法创建圆！！");
            }
        }
        private bool getCircleBy3P(Vector2d a, Vector2d b, Vector2d c, out Vector2d? center, out double radius)
        {
            double A, B, C, D;
            double x1 = a.X, x2 = b.X, x3 = c.X;
            double y1 = a.Y, y2 = b.Y, y3 = c.Y;
            //已知三个点确定圆的半径和圆心 
            double x1x1 = x1 * x1;
            double y1y1 = y1 * y1;
            double x2x2 = x2 * x2;
            double y2y2 = y2 * y2;
            double x3x3 = x3 * x3;
            double y3y3 = y3 * y3;

            double x2y3 = x2 * y3;
            double x3y2 = x3 * y2;

            double x2_x3 = x2 - x3;
            double y2_y3 = y2 - y3;

            double x1x1py1y1 = x1x1 + y1y1;
            double x2x2py2y2 = x2x2 + y2y2;
            double x3x3py3y3 = x3x3 + y3y3;

            A = x1 * y2_y3 - y1 * x2_x3 + x2y3 - x3y2;
            if (A == 0)
            {
                center = null;
                radius = 0;
                return false;
            }
            B = x1x1py1y1 * (-y2_y3) + x2x2py2y2 * (y1 - y3) + x3x3py3y3 * (y2 - y1);
            C = x1x1py1y1 * x2_x3 + x2x2py2y2 * (x3 - x1) + x3x3py3y3 * (x1 - x2);
            D = x1x1py1y1 * (x3y2 - x2y3) + x2x2py2y2 * (x1 * y3 - x3 * y1) + x3x3py3y3 * (x2 * y1 - x1 * y2);

            var x = -B / (2 * A);
            var y = -C / (2 * A);
            radius = Math.Sqrt((B * B + C * C - 4 * A * D) / (4 * A * A));
            center = new Vector2d(x, y);
            return true;
        }

        public override void CreateElement(LcElement element, Matrix3d matrix)
        {
            DocumentManager.CurrentRecorder.BeginAction("Circle");
            var circle = element as LcCircle;
            this.vportRt.ActiveElementSet.AddCircle(matrix.MultiplyPoint(circle.Center), circle.Radius);
            DocumentManager.CurrentRecorder.EndAction();
        }
        public override void CreateElement(LcElement element, Vector2d basePoint, double scaleFactor)
        {
            var circle = element as LcCircle;
            Matrix3d matrix3 = Matrix3d.Scale(scaleFactor, basePoint);
            this.vportRt.ActiveElementSet.AddCircle(matrix3.MultiplyPoint(circle.Center), circle.Radius * scaleFactor);
        }
        public override void CreateElement(LcElement element, Vector2d basePoint, Vector2d scaleVector)
        {
            var circle = element as LcCircle;
            Matrix3d matrix3 = Matrix3d.Scale(scaleVector, basePoint);
            this.vportRt.ActiveElementSet.AddCircle(matrix3.MultiplyPoint(circle.Center), circle.Radius * Vector2d.Distance(basePoint, scaleVector));
        }
        public override void Draw(SKCanvas canvas, LcElement element, Matrix3d matrix)
        {
            var circle = element as LcCircle;
            var mcenter = matrix.MultiplyPoint(circle.Center);
            var medge = matrix.MultiplyPoint(circle.Center + new Vector2d(0, circle.Radius));
            var mradius = Vector2d.Distance(mcenter, medge);
            var center = this.vportRt.ConvertWcsToScr(mcenter).ToSKPoint();
            var radius = this.vportRt.ConvertWcsToScr(mradius);
            //get Layer color 
            bool isDragCopy = (matrix != Matrix3d.Zero);
            var pen = this.GetDrawPen(circle);
            if (pen == Constants.defaultPen)
            {
                //TODO:这里可以考虑将实线用颜色做KEY，对SKPaint进行缓存
                using (var elePen = new SKPaint { Color = new SKColor(element.GetColorValue()), IsStroke = true })
                {

                    canvas.DrawCircle(center, (float)radius, elePen);
                }
            }
            else
            {
                canvas.DrawCircle(center, (float)radius, pen);
            }

        }


        public override void DrawAuxLines(SKCanvas canvas)
        {
            var mp = this.vportRt.PointerMovedPosition.ToVector2d();
            var wcs_mp = this.vportRt.ConvertScrToWcs(mp);

            if (_methodName == "CR")
            {
                if (cr_center == null) return;

                var movePoint = mp.ToSKPoint();
                var center = this.vportRt.ConvertWcsToScr(cr_center.Value).ToSKPoint();

                if (center == movePoint) return;

                using (var pen = new SKPaint { Color = SKColors.Silver, IsStroke = true })
                {
                    canvas.DrawLine(center, movePoint, pen);
                    var radius = (_methodArg == "D") ? SKPoint.Distance(center, movePoint) / 2 : SKPoint.Distance(center, movePoint);
                    canvas.DrawCircle(center, radius, pen);
                }
            }
            else if (_methodName == "2P")
            {
                if (_point1 == null) return;

                var movePoint = mp.ToSKPoint();
                var p1 = this.vportRt.ConvertWcsToScr(_point1.Value).ToSKPoint();

                if (p1 == movePoint) return;

                using (var pen = new SKPaint { Color = SKColors.Silver, IsStroke = true })
                {
                    canvas.DrawLine(p1, movePoint, pen);
                    var cx = (p1.X + movePoint.X) / 2;
                    var cy = (p1.Y + movePoint.Y) / 2;
                    var r = SKPoint.Distance(p1, movePoint) / 2;
                    canvas.DrawCircle(cx, cy, r, pen);
                }
            }
            else if (_methodName == "3P")
            {
                if (_point1 == null) return;

                var movePoint = mp.ToSKPoint();
                var p1 = this.vportRt.ConvertWcsToScr(_point1.Value).ToSKPoint();
                //第二点
                if (_point2 == null)
                {
                    if (p1 == movePoint) return;
                    using (var pen = new SKPaint { Color = SKColors.Orange, IsStroke = true, PathEffect = Constants.SelectedEffect })
                    {
                        canvas.DrawLine(p1, movePoint, pen);

                    }
                }
                //第三点
                else
                {
                    var p2 = this.vportRt.ConvertWcsToScr(_point2.Value).ToSKPoint();
                    if (p2 == movePoint) return;
                    using (var pen = new SKPaint { Color = SKColors.Orange, IsStroke = true, PathEffect = Constants.SelectedEffect })
                    {
                        canvas.DrawLine(p2, movePoint, pen);

                    }
                    using (var pen = new SKPaint { Color = SKColors.White, IsStroke = true })
                    {
                        getCircleBy3P(p1.ToVector2d(), p2.ToVector2d(), movePoint.ToVector2d(), out var c, out var rd);
                        var cx = (float)c?.X; var cy = (float)c?.Y;
                        var r = (float)rd;
                        canvas.DrawCircle(cx, cy, r, pen);
                    }
                }

            }
        }

        #region Grip
        public override ControlGrip[] GetControlGrips(LcElement element)
        {
            var circle = element as LcCircle;
            var grips = new List<ControlGrip>();
            var gripCenter = new ControlGrip
            {
                Element = circle,
                Name = "Center",
                Position = circle.Center
            };
            grips.Add(gripCenter);

            var gripLeft = new ControlGrip
            {
                Element = circle,
                Name = "Left",
                Position = new Vector2d(circle.Center.X - circle.Radius, circle.Center.Y)
            };
            grips.Add(gripLeft);

            var gripTop = new ControlGrip
            {
                Element = circle,
                Name = "Top",
                Position = new Vector2d(circle.Center.X, circle.Center.Y + circle.Radius)
            };
            grips.Add(gripTop);

            var gripRight = new ControlGrip
            {
                Element = circle,
                Name = "Right",
                Position = new Vector2d(circle.Center.X + circle.Radius, circle.Center.Y)
            };
            grips.Add(gripRight);

            var gripBottom = new ControlGrip
            {
                Element = circle,
                Name = "Bottom",
                Position = new Vector2d(circle.Center.X, circle.Center.Y - circle.Radius)
            };
            grips.Add(gripBottom);
            return grips.ToArray();
        }
        private string _gripName;
        private Vector2d _position;
        private LcCircle _circle;

        public override void SetDragGrip(LcElement element, string gripName, Vector2d position, bool isEnd)
        {
            var circle = element as LcCircle;

            _circle = circle;
            if (!isEnd)
            {
                _gripName = gripName;
                _position = position;
            }
            else
            {
                if (gripName == "Center")
                    circle.Set(center: position);
                else
                {
                    var r = Vector2d.Distance(circle.Center, position);
                    circle.Set(radius: r);
                }
            }
        }


        public override void DrawDragGrip(SKCanvas canvas)
        {
            if (_circle == null) return;

            var center = this.vportRt.ConvertWcsToScr(_circle.Center);
            var r = this.vportRt.ConvertWcsToScr(_circle.Radius);
            if (_gripName == "Center")
                center = this.vportRt.ConvertWcsToScr(_position);
            else
            {
                var wr = Vector2d.Distance(_circle.Center, _position);
                r = this.vportRt.ConvertWcsToScr(wr);
            }
            canvas.DrawCircle(center.ToSKPoint(), (float)r, Constants.draggingPen);
        }

        #endregion

        public override List<PropertyObserver> GetPropertyObservers()
        {
            return new List<PropertyObserver>()
            {
                new PropertyObserver()
                {
                    Name = "CenterX",
                    DisplayName = "圆心 X 坐标",
                    Getter = (ele) => Math.Round((ele as LcCircle).Center.X, 4),
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var x = Convert.ToDouble(value);
                        var cen = new Vector2d(x, circle.Center.Y);
                        circle.Set(center : cen);
                    }
                },
                new PropertyObserver()
                {
                    Name = "CenterY",
                    DisplayName = "圆心 Y 坐标",
                    Getter = (ele) => Math.Round((ele as LcCircle).Center.Y, 4),
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var y = Convert.ToDouble(value);
                        var cen = new Vector2d(circle.Center.X, y);
                        circle.Set(center : cen);
                    }
                },
                new PropertyObserver()
                {
                    Name = "Radius",
                    DisplayName = "半径",
                    Getter = (ele) => Math.Round((ele as LcCircle).Radius, 4),
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var radius = Convert.ToDouble(value);
                        circle.Set(radius : radius);
                    }
                },
                new PropertyObserver()
                {
                    Name = "Diameter",
                    DisplayName = "直径",
                    Getter = (ele) => Math.Round((ele as LcCircle).Radius * 2, 4),
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var radius = Convert.ToDouble(value) / 2;
                        circle.Set(radius : radius);
                    }
                },
                new PropertyObserver()
                {
                    Name = "Circumference",
                    DisplayName = "周长",
                    Getter = (ele) => Math.Round((ele as LcCircle).Radius * 2 * Math.PI, 4),
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var radius = Convert.ToDouble(value) / 2 / Math.PI;
                        circle.Set(radius : radius);
                    }
                },
                new PropertyObserver()
                {
                    Name = "Area",
                    DisplayName = "面积",
                    Getter = (ele) =>
                    {
                        var radius = (ele as LcCircle).Radius;
                        return Math.Round(Math.Pow(radius, 2) * Math.PI, 4);
                    },
                    Setter = (ele, value) =>
                    {
                        var circle = (ele as LcCircle);
                        var area = Convert.ToDouble(value);
                        var radius = Math.Sqrt(area / Math.PI);
                        circle.Set(radius : radius);
                    }
                },
                new PropertyObserver()
                {
                    Name = "NormalX",
                    DisplayName = "法向 X 坐标",
                    Getter = (ele) => 0
                },
                new PropertyObserver()
                {
                    Name = "NormalY",
                    DisplayName = "法向 Y 坐标",
                    Getter = (ele) => 0
                },
                new PropertyObserver()
                {
                    Name = "NormalZ",
                    DisplayName = "法向 Z 坐标",
                    Getter = (ele) => 1
                },
            };
        }
        /// <summary>
        /// 判断直线和圆的交点
        /// </summary>
        public bool GetIntersectionCircle(double startx, double starty, double endx, double endy, double centerx, double centery, double Radius, ref double[] ptInter1, ref double[] ptInter2)
        {
            double EPS = 0.01;  //误差允许范围
            double Radius2 = Radius * Radius;

            ptInter1[0] = ptInter2[0] = 65536.0;
            ptInter2[1] = ptInter2[1] = 65536.0;

            //求线段的长度
            double fDis = Math.Sqrt((endx - startx) * (endx - startx) + (endy - starty) * (endy - starty));
            double[] d = new double[2];
            d[0] = (endx - startx) / fDis;
            d[1] = (endy - starty) / fDis;
            double[] E = new double[2];
            E[0] = centerx - startx;
            E[1] = centery - starty;
            double a = E[0] * d[0] + E[1] * d[1];
            double a2 = a * a;
            double e2 = E[0] * E[0] + E[1] * E[1];
            if ((Radius2 - e2 + a2) < 0)
            {
                return false;
            }
            else
            {
                double f = System.Math.Sqrt(Radius2 - e2 + a2);
                double t = a - f;
                if (((t - 0.0) > -EPS) && (t - fDis) < EPS)
                {
                    ptInter1[0] = Math.Round(startx + t * d[0], 2);
                    ptInter1[1] = Math.Round(starty + t * d[1], 2);
                }
                t = a + f;
                if (((t - 0.0) > -EPS) && (t - fDis) < EPS)
                {
                    ptInter2[0] = Math.Round(startx + t * d[0], 2);
                    ptInter2[1] = Math.Round(starty + t * d[1], 2);
                }
                return true;
            }
        }

        public override SnapPointResult SnapPoint(SnapRuntime snapRt, LcElement element, Vector2d point, bool forRef)
        {

            var maxDistance = vportRt.GetSnapMaxDistance();
            LcCircle cir = element as LcCircle;
            var sscur = SnapSettings.Current;
            var result = new SnapPointResult { Element = element };
            var icir = cir.GetCurves()[0];

            if (SnapSettings.Current.ObjectOn)
            {

                if (sscur.PointType.Has(SnapPointType.Center))
                {
                    if ((point - cir.Center).Length <= maxDistance)
                    {
                        result.Point = cir.Center;
                        result.Name = "Center";

                        result.Curves.Add(new SnapRefCurve(SnapPointType.Center, icir));
                    }
                }

                if (sscur.PointType.Has(SnapPointType.Quadrant))
                {
                    foreach (var item in GetControlGrips(element))
                    {
                        if (item.Name != "Center")
                        {
                            if ((point - item.Position).Length <= maxDistance)
                            {
                                result.Point = item.Position;
                                result.Name = item.Name;
                                result.Curves.Add(new SnapRefCurve(SnapPointType.Quadrant, icir));
                            }
                        }
                    }
                }
                if (!forRef && result.Point == null)
                {
                    var distance = Vector2d.Distance(point, cir.Center);
                    if (sscur.PointType.Has(SnapPointType.Nearest))
                    {

                        if (Math.Abs(cir.Radius - distance) < maxDistance)
                        {
                            var dir = (point - cir.Center).Normalized;
                            var nearest = cir.Center + dir * cir.Radius;
                            result.Point = nearest;
                            result.Name = "Nearest";
                            result.Curves.Add(new SnapRefCurve(SnapPointType.Nearest, icir));
                        }
                    }

                }

            }

            if (result.Point != null)
                return result;
            else
                return null;
        }
    }
}
